/*
 *  Copyright (c) 2022 ZhuHai Jieli Technology Co.,Ltd.
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

#include "asm/rtc.h"
#include "asm/includes.h"
#include "asm/cpu.h"
#include "asm/power/p33.h"
#include "power_hw.h"
#include "system/sys_time.h"
#include "ioctl_cmds.h"

#define FPGA_SYS_TIME   0

#define YEAR        1927U
#define MONTH       1
#define DAY         1
#define HOUR        0
#define MINUTES     0
#define SECOND      0

#define WRITE_ALARM     BIT(1)
#define READ_ALARM      BIT(5)

#define WRITE_RTC       BIT(0)
#define READ_RTC        BIT(4)

#define IRTC_X2IE(x)      (JL_P33->RTC_CON = ((JL_P33->RTC_CON & ~(BIT(1))) | ((x & 0x01) << 1)))
#define IRTC_X512IE(x)    (JL_P33->RTC_CON = ((JL_P33->RTC_CON & ~(BIT(2))) | ((x & 0x01) << 2)))
#define IRTC_WKIE(x)      (JL_P33->RTC_CON = ((JL_P33->RTC_CON & ~(BIT(4))) | ((x & 0x01) << 4)))

// CONST
// const u16 year_tab[]={365,366};
const u8 month_tab1[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};         ///<非闰年每月的天数
const u8 month_tab2[] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};         ///<闰年每月的天数

const u16 smonth_tab1[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};     ///<非闰年的当月累计天数
const u16 smonth_tab2[] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366};     ///<    闰年的当月累计天数

typedef struct _RTC_VAR {
    struct rtc_dev_data *data;
    struct device device;
} RTC_VAR;

#define __this     (&rtc_var)
static RTC_VAR rtc_var;

#if FPGA_SYS_TIME
static int test_timer = 0;
static int test_sec = 0;

static struct sys_time test_sys_time = {
    .sec = 0,
    .min = 0,
    .hour = 8,
    .day = 1,
    .month = 1,
    .year = 2018,
};
#endif

void day_to_ymd(u16 day, struct sys_time *sys_time);
u16 ymd_to_day(struct sys_time *time);

u8 read_RTC_POR_FLAG(void)
{
    u8 ret;

    ret = p33_rx_1byte(R3_RST_CON);
    log_i("read_RTC_POR_FLAG = 0x%x \r\n", ret);

    return ret & BIT(7);
}

void clr_RTC_POR_FLAG(void)
{

    u8 dat;
    dat = p33_rx_1byte(R3_RST_CON);
    dat |= BIT(7);
    p33_tx_1byte(R3_RST_CON, dat);

}

/**@brief     读IRTC
   @param     cmd：读指令 r_time：读出的日期
*/
static void read_IRTC(u8 cmd, struct sys_time *r_time)
{
    u16 r_day;

    p33_cs_h(R3_RTC_CON0);
    p33_buf((P33_OR << 5) | (u8)((R3_RTC_CON0 & 0x300) >> 8));               // rd    //adr 3
    p33_buf((u8)(R3_RTC_CON0 & 0xff));       // wr    //adr 3
    p33_buf(cmd);

    p33_buf(0x0);
    r_day = p33_buf(0x0);
    r_day <<= 8;
    r_day += p33_buf(0x0);

    day_to_ymd(r_day, r_time);  // add：总天数换成：年-月-日

    r_time->hour = p33_buf(0x0);
    r_time->min  = p33_buf(0x0);
    r_time->sec  = p33_buf(0x0);

    // add：day day hour minute second
    // add: 按照由高到低，一次读1个byte
    p33_cs_l;
}

/**@brief     写IRTC
   @param     cmd：写指令 w_time:写入日期
*/
static void write_IRTC(u8 cmd, struct sys_time *w_time)
{
    u8 buf;
    u16 w_day;

    p33_cs_h(R3_RTC_CON0);
    p33_buf((u8)((R3_RTC_CON0 & 0x300) >> 8));               // rd    //adr 3
    p33_buf((u8)(R3_RTC_CON0 & 0xff));       // wr    //adr 3
    p33_buf(cmd);

    p33_buf(0);

    w_day = ymd_to_day(w_time);
    buf = (u8)(w_day >> 8);
    p33_buf(buf);
    buf = (u8)(w_day);
    p33_buf(buf);

    buf = w_time->hour;
    p33_buf(buf);

    buf = w_time->min;
    p33_buf(buf);

    buf = w_time->sec;
    p33_buf(buf);

    p33_cs_l;
}

int rtc_port_pr_in(u8 port)
{
    u8 dat;

    if (port > 1) {
        return -EINVAL;
    }

    dat = p33_rx_1byte(R3_PR_DIR);
    dat |= BIT(port);
    p33_tx_1byte(R3_PR_DIR, dat);

    return 0;
}

int rtc_port_pr_out(u8 port, bool on)
{
    u8 dat;

    if (port > 1) {
        return -EINVAL;
    }

    dat = p33_rx_1byte(R3_PR_OUT);
    dat &= ~BIT(port);

    if (on) {
        dat |= BIT(port);
    } else {
        dat &= ~BIT(port);
    }

    p33_tx_1byte(R3_PR_OUT, dat);

    dat = p33_rx_1byte(R3_PR_DIR);
    dat &= ~BIT(port);
    p33_tx_1byte(R3_PR_DIR, dat);

    return 0;
}

int rtc_port_pr_read(u8 port)
{
    u8 dat;

    if (port > 1) {
        return -EINVAL;
    }

    dat = p33_rx_1byte(R3_PR_IN);

    return (dat >> port) & 0x1;
}

int rtc_port_pr_hd(u8 port, bool on)
{
    u8 dat;

    if (port > 1) {
        return -EINVAL;
    }

    dat = p33_rx_1byte(R3_PR_HD);

    if (on) {
        dat |= BIT(port);
    } else {
        dat &= ~BIT(port);
    }

    p33_tx_1byte(R3_PR_HD, dat);

    return 0;
}

int rtc_port_pr_pu(u8 port, bool on)
{
    u8 dat;

    if (port > 1) {
        return -EINVAL;
    }

    dat = p33_rx_1byte(R3_PR_PU);

    if (on) {
        dat |= BIT(port);
    } else {
        dat &= ~BIT(port);
    }

    p33_tx_1byte(R3_PR_PU, dat);

    return 0;
}

int rtc_port_pr_pd(u8 port, bool on)
{
    u8 dat;

    if (port > 1) {
        return -EINVAL;
    }

    dat = p33_rx_1byte(R3_PR_PD);

    if (on) {
        dat |= BIT(port);
    } else {
        dat &= ~BIT(port);
    }

    p33_tx_1byte(R3_PR_PD, dat);

    return 0;
}

int rtc_port_pr_die(u8 port, bool on)
{
    u8 dat;

    if (port > 1) {
        return -EINVAL;
    }

    dat = p33_rx_1byte(R3_PR_DIE);

    if (on) {
        dat |= BIT(port);
    } else {
        dat &= ~BIT(port);
    }

    p33_tx_1byte(R3_PR_DIE, dat);

    return 0;
}

int rtc_port_pr_wkup_edge(u8 port, bool up_down)
{
    u8 dat;

    if (port > 1) {
        return -EINVAL;
    }

    dat = p33_rx_1byte(R3_WKUP_EDGE);

    if (up_down) {
        dat |= BIT(port);
    } else {
        dat &= ~BIT(port);
    }

    p33_tx_1byte(R3_WKUP_EDGE, dat);

    return 0;
}

int rtc_port_pr_wkup_en_port(u8 port, bool en)
{
    u8 dat;

    if (port > 1) {
        return -EINVAL;
    }

    dat = p33_rx_1byte(R3_WKUP_EN);

    if (en) {
        dat |= BIT(port);
    } else {
        dat &= ~BIT(port);
    }

    p33_tx_1byte(R3_WKUP_EN, dat);

    return 0;
}

int rtc_port_pr_wkup_pnd(u8 port)
{
    u8 dat;

    if (port > 1) {
        return -EINVAL;
    }

    dat = p33_rx_1byte(P3_WKUP_PND); // read pnd

    return (dat & BIT(port));
}

int __rtc_port_pr_wkup_clear_pnd(void)
{
    u8 sfr = p33_rx_1byte(R3_WKUP_PND);

    if (sfr & BIT(0)) {
        p33_tx_1byte(R3_WKUP_CPND, BIT(0));
    }

    if (sfr & BIT(1)) {
        p33_tx_1byte(R3_WKUP_CPND, BIT(1));
    }
    return 0;
}

int rtc_port_pr_wkup_clear_pnd(u8 port)
{
    u8 dat;

    if (port > 1) {
        return -EINVAL;
    }

    dat = p33_rx_1byte(R3_WKUP_PND);
    dat |= BIT(port); // clear pnd

    p33_tx_1byte(R3_WKUP_CPND, dat);

    return 0;
}

void set_alarm_ctrl(u8 set_alarm)
{
    u8 dat = p33_rx_1byte(R3_ALM_CON);

    if (set_alarm) {
        dat |= BIT(0);
    } else {
        dat &= ~BIT(0);
    }

    p33_tx_1byte(R3_ALM_CON, dat);
}

static void write_sys_time(struct sys_time *curr_time)
{
    write_IRTC(WRITE_RTC, curr_time);
}

static void read_sys_time(struct sys_time *curr_time)
{
#if FPGA_SYS_TIME
    memcpy_s(curr_time, sizeof(*curr_time), &test_sys_time, sizeof(*curr_time));
#else
    read_IRTC(READ_RTC, curr_time);
#endif
}

static void write_alarm(struct sys_time *alarm_time)
{
    write_IRTC(WRITE_ALARM, alarm_time);
}

static void read_alarm(struct sys_time *alarm_time)
{
    read_IRTC(READ_ALARM, alarm_time);
}

/**@brief    判断闰年
   @param     year:年份
   @return  真假
*/
bool leapyear(u16 year)
{
    if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
        return true;
    } else {
        return false;
    }
}

/**@brief    年份换算为天数
   @param     day：总日数
   @return  星期的日数
*/
u16 year_to_day(u16 year)
{
    if (leapyear(year)) {
        return 366;
    } else {
        return 365;
    }
}

/**@brief    月份换算为天数
   @param     day：总日数
   @return  星期的日数
*/
u16 month_to_day(u16 year, u8 month)
{
    if (leapyear(year)) {
        return smonth_tab2[month];
    } else {
        return smonth_tab1[month];
    }
}

u16 __month_to_day(u16 year, u8 month)
{
    if (leapyear(year)) {
        return month_tab2[month];
    } else {
        return month_tab1[month];
    }
}

/**@brief    总天数换算为年份/月份/日期
   @param     day：总天数；time:当前日期
   @return  星期的天数
*/
void day_to_ymd(u16 day, struct sys_time *sys_time)
{
    u8 tmp;

    tmp = 0;

    while (day >= year_to_day(YEAR + tmp)) {
        day -= year_to_day(YEAR + tmp);
        tmp++;
    }

    sys_time->year = YEAR + tmp;

    tmp = 0;

    while (day >= month_to_day(sys_time->year, MONTH + tmp)) {
        tmp++;
    }

    day -= month_to_day(sys_time->year, MONTH + tmp - 1);
    sys_time->month = MONTH + tmp;

    sys_time->day = DAY + day;
}

/**@brief    当前年份/月份/日期换算成总天数
   @param     time:当前日期
   @return  星期的天数
*/
u16 ymd_to_day(struct sys_time *time)
{
    u16 tmp_year;
    u16 tmp_day = 0;

    // 年->日，当年不算在内，所以是<
    for (tmp_year = YEAR; tmp_year < time->year; tmp_year++) {
        tmp_day += year_to_day(tmp_year);
    }

    if (leapyear(time->year)) {                    // 月->日
        tmp_day += smonth_tab2[time->month - 1];
    } else {
        tmp_day += smonth_tab1[time->month - 1];
    }

    tmp_day += (time->day - 1);                        // 日->日,当日不算在内，所以日应该减1

    return tmp_day;
}

static u8 caculate_weekday_by_time(struct sys_time *r_time)
{
    u16 y;
    u8 m;
    u8 d;

    y = r_time->year;
    m = r_time->month;
    d = r_time->day;

    if (m == 1 || m == 2) {
        m += 12;
        y--;
    }

    return ((d + 2 * m + 3 * (m + 1) / 5 + y + y / 4 - y / 100 + y / 400) % 7 + 1); // 1~7  星期1 ~星期7
}

void __attribute__((weak)) set_rtc_default_time(struct sys_time *t)
{
    t->year = 2019;
    t->month = 5;
    t->day = 5;
    t->hour = 18;
    t->min = 18;
    t->sec = 18;
}

void alarm_set_time(struct sys_time *time)
{
    set_alarm_ctrl(0);

    write_alarm(time);

    set_alarm_ctrl(1);
}

void alarm_set_time_sec(int _sec)
{
    struct sys_time time;
    u32 day;
    u8 hour, min, sec;

    sec = _sec % 60;
    min = _sec / 60 % 60;
    hour = _sec / 60 / 60 % 24;
    day = _sec / 60 / 60 / 24;

    set_alarm_ctrl(0);
    read_sys_time(&time);

    time.sec += sec;

    if (time.sec >= 60) {
        time.sec -= 60;
        time.min += 1;
    }

    time.min += min;

    if (time.min >= 60) {
        time.min -= 60;
        time.hour += 1;
    }

    time.hour += hour;

    if (time.hour >= 24) {
        time.hour -= 24;
        time.day += 1;
    }

    day += ymd_to_day(&time);
    day_to_ymd(day, &time);

    write_alarm(&time);

    set_alarm_ctrl(1);
}

void __attribute__((weak)) alm_wakeup_isr(void)
{
}

static u8 alarm_wakeup_flag = 0;
u8 get_alarm_wkup_flag(void)
{
    return alarm_wakeup_flag;
}

void set_alarm_wkup_flag(u8 flag)
{
    alarm_wakeup_flag = flag;
}

void rtc_wakup_source(void)
{
    u8 sfr = p33_rx_1byte(R3_WKUP_SRC);
    if (sfr & BIT(R3_WKUP_SRC_ALM)) {
        alarm_wakeup_flag = 1;
        alm_wakeup_isr();
        set_alarm_ctrl(0);
    }

    if (sfr & BIT(R3_WKUP_SRC_FLAG)) {

        P33_CON_SET(R3_RST_CON, 6, 1, 1);
    }
    if (sfr & BIT(R3_WKUP_SRC_PORT)) {
        __rtc_port_pr_wkup_clear_pnd();
    }
    if (sfr & BIT(R3_WKUP_SRC_512HZ)) {
        TIME_256HZ_CPND(1);
    }
    if (sfr & BIT(R3_WKUP_SRC_64HZ)) {
        TIME_64HZ_CPND(1);
    }
    if (sfr & BIT(R3_WKUP_SRC_2HZ)) {
        TIME_2HZ_CPND(1);
    }
    if (sfr & BIT(R3_WKUP_SRC_1HZ)) {
        TIME_1HZ_CPND(1);
    }
}

/*
*Description: 复位RTC时间
*/
void reset_rtc(void)
{
    u16 magic;

    struct sys_time current_time;
    u16 year;
    u8 month;
    u8 day;
    u8 hour;
    u8 min;
    u8 sec;
    read_sys_time(&current_time);

    log_i("rtc_read_sys_time: %d-%d-%d %d:%d:%d\n",
          current_time.year,
          current_time.month,
          current_time.day,
          current_time.hour,
          current_time.min,
          current_time.sec);

    if (!read_RTC_POR_FLAG()) {
        return;
    }

    clr_RTC_POR_FLAG();

    set_rtc_default_time(&current_time);

    write_sys_time(&current_time);

    read_sys_time(&current_time);

    log_i("rtc_read_default_time: %d-%d-%d %d:%d:%d\n",
          current_time.year,
          current_time.month,
          current_time.day,
          current_time.hour,
          current_time.min,
          current_time.sec);
}

#if FPGA_SYS_TIME
static void sys_time_run(void *arg)
{
    test_sys_time.sec++;

    if (test_sys_time.sec >= 60) {
        test_sys_time.sec -= 60;
        test_sys_time.min += 1;

        if (test_sys_time.min == 60) {
            test_sys_time.min = 0;
            test_sys_time.hour += 1;

            if (test_sys_time.hour == 24) {
                test_sys_time.hour = 0;
                u32 day = ymd_to_day(&test_sys_time);
                day_to_ymd(day + 1, &test_sys_time);
            }
        }
    }
}
#endif

static int rtc_open(const char *name, struct device **device, void *arg)
{
    *device = &__this->device;

#if FPGA_SYS_TIME

    if (!test_timer) {
        test_timer = sys_timer_add(NULL, sys_time_run, 1000);
    }

#endif
    return 0;
}

static int rtc_ioctl(struct device *device, u32 cmd, u32 arg)
{
    int err = 0;
    struct _pr_wkup *wkup;
    struct _pr_main_en *wkup_mian;
    unsigned int gpio;

    switch (cmd) {
        case IOCTL_GET_WEEKDAY:
            err = caculate_weekday_by_time((struct sys_time *)arg);
            break;

        case IOCTL_GET_SYS_TIME:
            read_sys_time((struct sys_time *)arg);
            break;

        case IOCTL_SET_SYS_TIME:
            write_sys_time((struct sys_time *)arg);
            break;

        case IOCTL_SET_ALARM:
            write_alarm((struct sys_time *)arg);
            break;

        case IOCTL_GET_ALARM:
            read_alarm((struct sys_time *)arg);
            break;

        case IOCTL_SET_VDD50_EN:
            break;
        case IOCTL_SET_ALARM_ENABLE:
            if (arg) {
                set_alarm_ctrl(1);
            } else {
                set_alarm_ctrl(0);
            }
            break;
        default:
            break;
    }

    return err;
}

/*
*Description: RTC初始化
*/

static int rtc_init(const struct dev_node *node, void *arg)
{
    __this->data = (struct rtc_dev_data *)arg;

    u8 dat;

    if (__this->data) {
        P33_CON_SET(P3_ANA_CON2, 0, 3, 0b001);        // 使用内部LDO，保证上电清PND
    }

    IRTC_X2IE(0);
    IRTC_X512IE(0);
    IRTC_WKIE(1);                      // WKUPIE 允许置1，否则 0
    JL_P33->RTC_CON |= BIT(13) | BIT(14) ; // | BIT(15); //BIT(15)清掉了就不能检查到关机闹钟唤醒

    ALM_CLK_SEL(__this->data->clk_res);

    dat = p33_rx_1byte(R3_OSL_CON);
    dat |= BIT(0); // doscen 晶振两脚模式,只用于32k晶振
    dat |= BIT(1); // doscen 晶振两脚模式,只用于32k晶振
    p33_tx_1byte(R3_OSL_CON, dat);

    dat = p33_rx_1byte(P3_OSL_CON);
    dat |= BIT(0); // doscen 晶振两脚模式,只用于32k晶振
    dat |= BIT(1); // doscen 晶振两脚模式,只用于32k晶振
    p33_tx_1byte(P3_OSL_CON, dat);

    reset_rtc();

    p33_or_1byte(P3_VLD_KEEP, BIT(7)); // 连接到P33，否则会导致闹钟中断进不去

    if (__this->data && __this->data->port_en) {
        puts("++  rtc port wakeup   \n");
        if ((__this->data->port < IO_PORT_PR_00) && (__this->data->port > IO_PORT_PR_01)) {
            ASSERT(0, "rtc pr over size \n");
        }
        u8 port = __this->data->port - IO_PORT_PR_00;
        rtc_port_pr_wkup_clear_pnd(port);
        rtc_port_pr_wkup_en_port(port, 1);
        rtc_port_pr_wkup_edge(port, __this->data->edge);
    }

    rtc_port_pr_in(0);
    rtc_port_pr_pu(0, 0);
    rtc_port_pr_pd(0, 0);
    rtc_port_pr_die(0, 0);


    rtc_port_pr_in(1);
    rtc_port_pr_pu(1, 0);
    rtc_port_pr_pd(1, 0);
    rtc_port_pr_die(1, 0);

    if (__this->data && (!__this->data->rtc_ldo)) {
        P33_CON_SET(P3_ANA_CON2, 0, 3, 0b000);        // 如果不使用内部LDO，清PND后关闭
    }

    return 0;
}

void rtc_test_read(void)
{
    struct sys_time time;

    read_sys_time(&time);

    log_i("time: %d-%d-%d %d:%d:%d\n", time.year, time.month, time.day,
          time.hour, time.min, time.sec);
}

void rtc_test(void)
{
    struct sys_time time;

    read_sys_time(&time);

    log_i("time: %d-%d-%d %d:%d:%d\n", time.year, time.month, time.day,
          time.hour, time.min, time.sec);

    time.year = 2017;
    time.month = 4;
    time.day = 7;

    write_sys_time(&time);

    memset_s(&time, sizeof(time), 0, sizeof(time));
    read_sys_time(&time);

    log_i("time: %d-%d-%d %d:%d:%d\n", time.year, time.month, time.day,
          time.hour, time.min, time.sec);
}

const struct device_operations rtc_dev_ops = {
    .init   = rtc_init,
    .open   = rtc_open,
    .ioctl  = rtc_ioctl,
    .read   = NULL,
    .write  = NULL,
};

